// game class   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// game class - init with pgn
chess_spider.Game = function( pgn, pgn_url, mode ) {

	// base stuff
	this.pgn_url = pgn_url;
	this.mode = mode;
	this.restoreMoves = {}
	this.headers = {};

	// add ourselves to game_arr (attach now to help debugging)
	chess_spider.state.game_arr.push( this );
	this.idx = chess_spider.state.game_arr.indexOf( this );

	// pull out headers out of text
	var pgn_arr = pgn.split("\n");
	var idx = 0;
	for( idx=0; idx<pgn_arr.length; idx++ ) {

		// ignore empty lines
		if( chess_spider.regx.blank_line.test( pgn_arr[idx] ) ) continue;

		// see if we have meta data
		var match_arr = chess_spider.regx.meta.exec( pgn_arr[idx] );
		if( match_arr && match_arr[1] ) {
			this.headers[match_arr[1]] = ( this.mode == 'user' ) 
				? { 'uvalue': match_arr[2].trim(), 'mode': 'new' }
				: { 'value':  match_arr[2].trim(), 'mode': 'pgn' };
		}

		// no meta - get out of headers
		else	break;
	}

	// see it pgn_url came over
	if( !this.pgn_url && this.headerExists('Source') ) {
		this.pgn_url = this.headerValue('Source');
		if( !chess_spider.params.pgn_url ) chess_spider.params.pgn_url = this.pgn_url;
	}

	// initial pstn
	this.pstn = (this.headerExists('OrigPstn')) ? this.headerValue('OrigPstn') : chess_spider.params.orig_pstn;
	this.first_mv = (this.headerExists('FirstMove')) ? this.headerValue('FirstMove') : '1w';

	// set this.pgn to move section of pgn
	this.pgn = pgn_arr.slice(idx).join(' ');
	this.root_mv = null;
	this.dom_movelist = null;
}

// game gc
chess_spider.Game.prototype.gc = function() {

	// remove self from fixedboards
	fb_arr = chess_spider.FixedBoard.fixedboards();
	for( var i=0; i<fb_arr.length; i++ ) {
		if( fb_arr[i].board.game_arr[this.idx] != undefined ) delete fb_arr[i].board.game_arr[this.idx];
		if( fb_arr[i].root_mv && fb_arr[i].root_mv.game == this ) fb_arr[i].root_mv = null;
		if( fb_arr[i].cur_mv && fb_arr[i].cur_mv.game == this ) fb_arr[i].cur_mv = null;
	}

	// remove self from movelists
	ml_arr = chess_spider.MoveList.movelists();
	for( var i=0; i<ml_arr.length; i++ ) {
		if( ml_arr[i].game == this && ml_arr[i].dom_div ) ml_arr[i].dom_div.removeChild(ml_arr[i].dom_div.firstChild);
	}

	// remove references to game's moves
	for( var mv=this.root_mv; mv; mv=mv.nxt ) chess_spider.state.moves[mv.id];
}

// add moves to game from pgn
chess_spider.Game.prototype.parsePgnMoves = function( work_pgn ) {

	// root_mv should be null
	if( this.root_mv ) throw( 'parsePgnMoves - already root_mv' );

	// moves - set work_pgn as pgn as one string
	this.root_mv = new chess_spider.Move( this, null,
				((this.first_mv.substr(1,1)=='w')?parseInt(this.first_mv.substr(0,1))-1:parseInt(this.first_mv.substr(0,1))),
				((this.first_mv.substr(1,1)=='w')?'b':'w'),
				'- -', 0, null, null, 'pgn' );

	// track these manually
	var level = this.root_mv.level+1;
	var prv = this.root_mv;
	//~var work_pgn = this.pgn.replace( /^.*\] +/, '' );

	// get all we can at this level
	//-while( this.idx_sq < 86 ) {
	while( work_pgn.trim() ) {

		// subtree
		var match_arr = chess_spider.regx.subtree( work_pgn );
		if( match_arr && match_arr[0] ) {
			work_pgn = match_arr[2];
			//-console.log( 'subtree1 '+prv.idx+' '+match_arr[1] );
			if( match_arr[1]=='(' )		level += 1;
			else if( match_arr[1]==')' ) 	level -= 1;
			continue;
		}

		// pre-comment
		var match_arr = chess_spider.regx.comment( work_pgn );
		if( match_arr && match_arr[0] ) {
			//-alert( "pre_comment: "+match_arr[1].trim() );
			var pre_comment = match_arr[1].trim();
			work_pgn = match_arr[2];
		}
		else	var pre_comment = null;

		// white skip
		var match_arr = chess_spider.regx.white_skip( work_pgn );
		if( match_arr && match_arr[0] ) {
			//-alert( "white skip: " + work_pgn );
			var move_no = parseInt(match_arr[1]);
			work_pgn = match_arr[2];
			var skip = true;
		}
		else	var skip = false;

		// white move
		if( !skip ) {
			var match_arr = chess_spider.regx.white_move( work_pgn );
			if( !match_arr || !match_arr[0] ) throw( 'expected to see white move: game='+this.idx+', level='+level+', pgn="'+escape(work_pgn.substr(0,40))+'..."' );
			//-console.log( 'white: move='+match_arr[1]+', move='+match_arr[1] );
			var move_no = match_arr[1];
			var move = new chess_spider.Move( this, prv, move_no, 'w', match_arr[2], level, pre_comment, null, 'pgn' );
			work_pgn = match_arr[3];

			// nar
			var match_arr = chess_spider.regx.nar( work_pgn );
			if( match_arr && match_arr[0] ) {
				move.nar = parseInt(match_arr[1]);
				work_pgn = match_arr[2];
			}

			// finish
			for( var i=1; i<chess_spider.regx.finish_arr.length; i++ ) {
				var match_arr = chess_spider.regx.finish_arr[i]( work_pgn );
				if( match_arr && match_arr[0] ) {
					move.finish = i;
					work_pgn = match_arr[2];
					break;
				}
			}

			// post comment
			var match_arr = chess_spider.regx.comment( work_pgn );
			if( match_arr && match_arr[0] ) {
				move.comment = match_arr[1].trim();;
				work_pgn = match_arr[2];
			}
			else	move.comment = null;

			// finish - look again
			if( !move.finish ) for( var i=1; i<chess_spider.regx.finish_arr.length; i++ ) {
				var match_arr = chess_spider.regx.finish_arr[i]( work_pgn );
				if( match_arr && match_arr[0] ) {
					move.finish = i;
					work_pgn = match_arr[2];
					break;
				}
			}

			// prev is now move
			prv = move;

			// subtree
			var match_arr = chess_spider.regx.subtree( work_pgn );
			if( match_arr && match_arr[0] ) {
				work_pgn = match_arr[2];
				//-console.log( 'subtree2 '+prv.idx+' '+match_arr[1]+': '+work_pgn );
				if( match_arr[1]=='(' )		level += 1;
				else if( match_arr[1]==')' )	level -= 1;
				//-if( regx.black_skip.test(work_pgn) ) continue;
				continue;
			}
		}

		// pre-comment - if white skip, already set
		if( !skip ) {
			var match_arr = chess_spider.regx.comment( work_pgn );
			if( match_arr && match_arr[0] ) {
				var pre_comment = match_arr[1].trim();
				work_pgn = match_arr[2];
			}
			else 	var pre_comment = null;
		}

		// black move
		var match_arr = chess_spider.regx.black_move( work_pgn );
		if( !match_arr || !match_arr[0] ) break;
		//-if( !match_arr || !match_arr[0] ) throw( 'expected to see black move: '+work_pgn );
		var move = new chess_spider.Move( this, prv, move_no, 'b', match_arr[1], level, pre_comment, null, 'pgn' );
		work_pgn = match_arr[2];
		prv = move;
		//-alert( "move: move_no="+move_no+", color=b, move=" + move['move'] + ", work_pgn = " + work_pgn );

		// nar
		var match_arr = chess_spider.regx.nar( work_pgn );
		if( match_arr && match_arr[0] ) {
			move.nar = parseInt(match_arr[1]);
			work_pgn = match_arr[2];
		}

		// finish
		for( var i=1; i<chess_spider.regx.finish_arr.length; i++ ) {
			var match_arr = chess_spider.regx.finish_arr[i]( work_pgn );
			if( match_arr && match_arr[0] ) {
				move.finish = i;
				work_pgn = match_arr[2];
			}
		}

		// post comment
		var match_arr = chess_spider.regx.comment( work_pgn );
		if( match_arr && match_arr[0] ) {
			move.comment = match_arr[1].trim();;
			work_pgn = match_arr[2];
		}
		else	move.comment = null;

		// subtree
		match_arr = chess_spider.regx.subtree( work_pgn );
		if( match_arr && match_arr[0] ) {
			work_pgn = match_arr[2];
			if( match_arr[1]=='(' )		level += 1;
			else if( match_arr[1]==')' ) 	level -= 1;
			continue;
		}
	}

	// pull in moves
	this.restore();
}

// restore moves for all games
chess_spider.Game.restoreAll = function() {
	for( var i=0; i<chess_spider.state.game_arr.length; i++ ) chess_spider.state.game_arr[i].restore();
}

// restore moves for this game
chess_spider.Game.prototype.restore = function() {

	// makes no sense to restore if no root_mv
	if( !this.root_mv ) return;

	// whether to add to dom
	var add2dom = (this.root_mv.dom_href() != null);

	// restore moves
	for( m in this.restoreMoves ) 
	if( typeof this.restoreMoves[m] == 'object' ) {

		var move = this.restoreMoves[m];
		var game = this;

		// if already exist, update ucomment
		if( move.ucomment ) {
			chess_spider.state.moves[move.id].ucomment = moves.ucomment;
			if( add2dom ) chess_spider.state.moves[move.id].showComments();
		}
		// otherwise new move
		else {
			var prv = (move.prv) ? chess_spider.state.moves[move.prv] : null;
			// note: following works since root_mv will not be part of restore set
			var level = move.id.split(':').length;
			var branch_id = move.id.match(/B(.*)M/)[1];
			move.move = move.move.trim();
			if( chess_spider.state.ready ) console.log( 'id='+move.id+' => Move( '
					+game.idx+', '
					+'prv='+prv.id+', '
					+'move_no='+move.move_no+', '
					+move.color+', '
					+'move='+move.move+', '
					+'level='+level+', '
					+move.pre_comment+', '
					+move.comment+', user, '
					+branch_id+' )' );
			new chess_spider.Move( game, prv, move.move_no, move.color, move.move, level, move.pre_comment, move.comment, 'user', branch_id );
			chess_spider.state.moves[move.id].domAdd();
		}

		// remove from restoreMoves
		delete this.restoreMoves[m];
	}
}

// parse pgn - this will create games and put them into game_arr
// append: whether to append these games onto games already on board
// return success
chess_spider.Game.parsePgn = function( append ) {

	// user not ready while this is going on
	// todo: ready must have been set before parsePgn
	//-chess_spider.state.ready = false;

	// garbage collection - if removing games	
	if( !append ) {
		// gc all games
		for( var i=chess_spider.state.game_arr.length-1; i>=0; i-- ) chess_spider.state.game_arr[i].gc();
		// reset game_arr
		chess_spider.state.game_arr = new Array();
		// reset moves
		chess_spider.state.moves = {};
	}

	// inspect pgn
	if( !chess_spider.params.pgn || !chess_spider.params.pgn.length ) {
		alert( 'no pgn to load' );
		return false;
	}

	// sanitize
	chess_spider.params.pgn = chess_spider.params.pgn.replace(/\r/g,'');

	// pull into game arr
	// note:  will stick leading [ back on later
	var pgn_arr = chess_spider.params.pgn.split(/\n\s*\n\s*\[/);

	// report count and size
	var buf = '';
	for( var i=0; i<pgn_arr.length; i++ ) buf += pgn_arr[i].length + ',';
	console.log( pgn_arr.length + " game pgns: " + buf );

	// repopulate game_arr
	for( var game_idx=0; game_idx<pgn_arr.length; game_idx++ ) {
		// game will add itself to game_arr
		var new_game = new chess_spider.Game( ((game_idx>0)?'[':'')+pgn_arr[game_idx], chess_spider.params.pgn_url );
	}

	// make sure we got at least one game
	if( chess_spider.state.game_arr[0] == undefined || chess_spider.state.game_arr[0] == null ) {
		alert( 'no games loaded from PRN' );
		return false;
	}

	// get saves
	chess_spider.tasks.restorePullList( chess_spider.params.pgn_url );

	return true;
}

// helper functions
chess_spider.Game.display = function( game_idx, movelist_id, fixedboard_id ) {
	if( !chess_spider.state.game_arr[game_idx] ) {
		alert( 'chess_spider configuration error: no chess_spider.state.game_arr['+game_idx+'] for Game.display' );
		return;
	}
	chess_spider.state.game_arr[game_idx].display( movelist_id, fixedboard_id );
	chess_spider.FixedBoard.resetAllOptions();
}
chess_spider.Game.prototype.movelist = function() { return (this.movelist_id) ? document.getElementById(this.movelist_id) : null };
chess_spider.Game.prototype.fixedboard = function() { return (this.fixedboard_id) ? chess_spider.params.fixedboards[this.fixedboard_id] : null };
chess_spider.Game.prototype.dom_fixedboard = function() { return (this.fixedboard_id) ? document.getElementById(this.fixedboard_id) : null };
//=chess_spider.Game.prototype.dom_movelist = function() { return (this.movelist_id) ? document.getElementById(this.movelist_id) : null };
chess_spider.Game.prototype.headerExists = function(hdr) { return ( this.headers[hdr] ) ? true : false; }
chess_spider.Game.prototype.headerMode = function(hdr) { return ( this.headers[hdr] ) ? this.headers[hdr].mode : undefined; }
chess_spider.Game.prototype.headerValue = function(hdr) {
	if( !this.headers[hdr] )			return undefined;
	else if( this.headers[hdr].mode=='pgn' )	return this.headers[hdr].value;
	else						return this.headers[hdr].uvalue; };

// display game on page
chess_spider.Game.prototype.display = function( movelist_id, fixedboard_id ) {

	// make sure moves have been instantiated
	if( !this.root_mv ) this.parsePgnMoves( this.pgn );
	if( !this.root_mv ) throw( 'did not create root move!!' );

	// default args
	if( fixedboard_id == undefined ) fixedboard_id = this.fixedboard_id;
	this.fixedboard_id = fixedboard_id;
	if( movelist_id == undefined ) movelist_id = this.movelist_id;
	this.movelist_id = movelist_id;

	// fixedboard
	if( fixedboard_id ) {

		if( !chess_spider.params.fixedboards[fixedboard_id] == undefined ) {
			alert( 'chess_spider config error: expecting id="'+fixedboard_id+'" to be in fixedboards' );
			return;
		}

		if( !document.getElementById(fixedboard_id) ) {
			alert( 'chess_spider config error: expecting id="'+fixedboard_id+'" to be in document' );
			return;
		}

		// init fixedboard: set move, attach to dom
		chess_spider.params.fixedboards[fixedboard_id].board.setMove( this.root_mv );
		chess_spider.params.fixedboards[fixedboard_id].display( this );
	}

	// set up movelist
	if( this.movelist_id ) chess_spider.params.movelists[this.movelist_id].display( this );

	// set current board
	this.setCur();

	// global current board
	chess_spider.state.cur_bd = this.cur_bd;
}


chess_spider.Game.prototype.fullTitle = function() {
	var buf = '';

	// title
	if( this.headerExists('Title') ) {
		buf += '<b>' + this.headerValue('Title') + '</b><br/>';
		if( this.headerExists('Author') ) {
			buf += '<i>Author:</i> '+this.headerValue('Author');
			if( this.headerExists('CreateDate') ) buf += ' <i>on</i> '+this.headerValue('CreateDate');
		}
	}

	// white
	else if( this.headerExists('White') ) {
		buf += '<b>' + this.headerValue('White').firstName() + '</b> ';
		if( this.headerExists('WhiteElo') ) buf += ' (' + this.headerValue('WhiteElo') + ')';
		buf += ' <i>vs</i> <b>' + this.headerValue('Black').firstName() + '</b>';
		if( this.headerExists('BlackElo') ) buf += ' (' + this.headerValue('BlackElo') + ')';
		buf += '<br/>';
	}

	// event
	if( this.headerExists('Event') ) {
		buf += this.headerValue('Event');
		if( this.headerExists('Date') ) buf += ' - ' + this.headerValue('Date');
	}

	// date (no event)
	else if( this.headerExists('Date') ) {
		buf += '<i>Date:</i> ' + this.headerValue('Date');
	}

	return buf;
}

// title of game
chess_spider.Game.prototype.title = function() {

	// if title provided, use it
	if( this.headerExists('Title') )	return this.headerValue('Title');
	else if( this.headerExists('White') )	return this.headerValue('White').lastName() + ' - ' + this.headerValue('Black').lastName();
	else					return "Unknown Title for Game";
}

// set current board
chess_spider.Game.prototype.setCur = function( cur_board ) {

	// whether initiate call to setCur
	var initiate = ( !cur_board );

	// get boards from dom
	var board_arr = chess_spider.Board.boards();
	if( !board_arr.length ) return;

	// set cur_board if not set
	if( !cur_board ) {
		if( this.fixedboard_id )	cur_board = chess_spider.params.fixedboards[this.fixedboard_id].board;
		else				cur_board = board_arr[0];
	}

	//-console.log( 'game['+this.idx+'].setCur('+cur_board.root_mv.id+')' );

	// set board border width according to whether current bd
	for( i=0; i<board_arr.length; i++ ) {

		// not this game - ignore
		// todo: are we sure about this? or should setCur be global?
		if( !board_arr[i]
		||  !board_arr[i].root_mv
		||   board_arr[i].root_mv.game != this ) continue;

		// wide for current board
		board_arr[i].setCur( board_arr[i] == cur_board );

		// minimize board if initial call to setCur and exists fixedboard and this is dynamic board and not already game.cur_bd
		// todo: is this working?
		if( initiate && this.fixedboard_id && cur_board != board_arr[i] && !this.cur_bd ) board_arr[i].dom_table().style.display = 'none';

		// make sure we have screen focus
		window.location.hash = 'cs__B' + cur_board.seq + '-table';
		// todo: hide hash?
	}

	// set cur
	this.cur_bd = (cur_board) ? cur_board : board_arr[0];

	// if initiate, make sure new title on fixedboard
	if( initiate && this.fixedboard_id ) chess_spider.params.fixedboards[this.fixedboard_id].writeTitle();

	// set global cur_bd
	chess_spider.state.cur_bd = this.cur_bd;

	// make sure it's expanded
	this.cur_bd.dom_table().style.display = 'block';
}

// write user data into pgn
chess_spider.Game.prototype.writePgn = function() {

	// if not parsed, do so
	if( !this.root_mv ) this.parsePgnMoves( this.pgn );

	var buf = '';

	// headers
	// todo: add for user
	for( var h in this.headers )
	if( typeof this.headers[h] == 'object' ) {
		buf += '['+h+' "'+this.headerValue(h)+'"]\n';
	}
	buf += "\n";

	// go thru all level one branches (almost always only one)
	for( var mbuf='', i=0; i<this.root_mv.branch_arr.length; i++ ) {

		// all moves for level one branch
		for( var mv=this.root_mv.branch_arr[i]; mv && mv!=this.root_mv; mv=mv.nxt ) {

			// whether pre-number
			var pre = false;

			// end of branch(s) higher up
			for( var i=mv.prv.level; i> mv.level && mv.prv.level > 1; i-- ) {
				pre = true;
				mbuf += ") ";
			}

			// end of branch(s) sideways
			if( mv.level == mv.prv.level && mv.branch != mv.prv.branch ) {
				pre = true;
				mbuf += ") ";
			}

			// beginning of branch
			if( (mv.level > mv.prv.level || mv.level==mv.prv.level && mv.branch > mv.prv.branch) && mv.prv != this.root_mv ) {
				pre = true;
				mbuf += "( ";
			}

			// pre-comment
			if( mv.pre_comment ) {
				pre = true;
				mbuf += '{' + mv.pre_comment + '} ';
			}

			// move_no
			if( mv.color == 'w' ) mbuf += mv.move_no+'. ';
			else if( pre || mv.prv.comment || mv.prv.ucomment ) mbuf += mv.move_no+'... ';
			//-else if( mv.pre_comment || mv.prv.comment || mv.prv.ucomment || mv.branch_id() != move.prv.branch_id() ) mbuf += ' '+mv.move_no+'... ';

			// move, nar, finish
			if( mv.move ) mbuf += mv.move+' ';
			if( mv.nar ) mbuf += '$'+mv.nar+' ';
			if( mv.finish ) mbuf += chess_spider.regx.finish_arr[mv.finish];

			// comments
			if( mv.comment ) mbuf += '{' + mv.comment + '} ';

			// user comments
			if( mv.ucomment ) mbuf += ' {[user "'+((chess_spider.state.user.login)?chess_spider.state.user.login:'anon')+'"] ' + mv.ucomment + '} ';
		}
		mbuf += " \n";
	}
	return buf+mbuf.replace(/(([^\0]{1,78}) )/g,'$1\n');
}

// write user data into pgn
chess_spider.Game.writePgns = function() {
	var buf = '';
	for( var i=0; i<chess_spider.state.game_arr.length; i++ ) {
		buf += chess_spider.state.game_arr[i].writePgn();
		buf += "\n";
	}
	return buf;
}

